/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX SHARE

#include "share/ob_service_epoch_proxy.h"

#include "lib/mysqlclient/ob_mysql_result.h"
#include "lib/mysqlclient/ob_mysql_transaction.h"
#include "lib/mysqlclient/ob_mysql_proxy.h"
#include "share/inner_table/ob_inner_table_schema_constants.h"
#include "share/ob_dml_sql_splicer.h"
#include "share/ob_force_print_log.h"

namespace oceanbase
{
using namespace common;
using namespace common::sqlclient;
namespace share
{
int ObServiceEpochProxy::init_service_epoch(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const int64_t freeze_service_epoch,
    const int64_t arbitration_service_epoch)
{
  int ret = OB_SUCCESS;
  if (is_user_tenant(tenant_id)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id));
  // sys/meta tenant initialized freeze_service_epoch
  } else if (OB_FAIL(insert_service_epoch(sql_proxy, tenant_id, FREEZE_SERVICE_EPOCH, freeze_service_epoch))) {
    LOG_WARN("fail to init freeze_service_epoch", KR(ret), K(tenant_id), K(freeze_service_epoch));
  } else if (OB_FAIL(ObServiceEpochProxy::insert_service_epoch(
                         sql_proxy,
                         tenant_id,
                         ARBITRATION_SERVICE_EPOCH,
                         arbitration_service_epoch))) {
    LOG_WARN("fail to init arb service epoch", KR(ret), K(tenant_id), K(arbitration_service_epoch));
  } else if (is_meta_tenant(tenant_id)) {
    // user tenant initialized freeze_service_epoch
    const uint64_t user_tenant_id = gen_user_tenant_id(tenant_id);
    if (OB_FAIL(insert_service_epoch(sql_proxy, user_tenant_id, FREEZE_SERVICE_EPOCH, freeze_service_epoch))) {
      LOG_WARN("fail to init freeze_service_epoch", KR(ret), K(user_tenant_id), K(freeze_service_epoch));
    } else if (OB_FAIL(ObServiceEpochProxy::insert_service_epoch(
                           sql_proxy,
                           user_tenant_id,
                           ARBITRATION_SERVICE_EPOCH,
                           arbitration_service_epoch))) {
      LOG_WARN("fail to init arb service epoch", KR(ret), K(user_tenant_id), K(arbitration_service_epoch));
    }
  }
  return ret;
}

int ObServiceEpochProxy::insert_service_epoch(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const char *name,
    const int64_t epoch_value)
{
  int ret = OB_SUCCESS;

  int64_t affected_rows = 0;
  ObDMLSqlSplicer dml;
  ObSqlString sql;
  if (!is_valid_tenant_id(tenant_id) || OB_ISNULL(name)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id), KP(name));
  } else {
    const uint64_t meta_tenant_id = gen_meta_tenant_id(tenant_id);
    if (OB_FAIL(dml.add_pk_column("tenant_id", tenant_id))
        || OB_FAIL(dml.add_pk_column("name", name))
        || OB_FAIL(dml.add_column("value", epoch_value))) {
      LOG_WARN("fail to add column", KR(ret), K(name), K(epoch_value));
    } else if (OB_FAIL(dml.finish_row())) {
      LOG_WARN("fail to finish row", KR(ret), K(tenant_id));
    } else if (OB_FAIL(dml.splice_insert_sql(OB_ALL_SERVICE_EPOCH_TNAME, sql))) {
      LOG_WARN("fail to splice batch insert update sql", KR(ret), K(sql));
    } else if (OB_FAIL(sql_proxy.write(meta_tenant_id, sql.ptr(), affected_rows))) {
      LOG_WARN("fail to execute sql", KR(ret), K(tenant_id), K(meta_tenant_id), K(sql));
    } else {
      LOG_INFO("succ to insert service epoch", K(tenant_id), K(name), K(epoch_value), K(affected_rows));
    }
  }

  return ret;
}

int ObServiceEpochProxy::update_service_epoch(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const char *name,
    const int64_t epoch_value,
    int64_t &affected_rows)
{
  int ret = OB_SUCCESS;
  affected_rows = 0;
  if (!is_valid_tenant_id(tenant_id) || OB_ISNULL(name) || (epoch_value < 0)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id), KP(name), K(epoch_value));
  } else {
    const uint64_t meta_tenant_id = gen_meta_tenant_id(tenant_id);
    ObSqlString sql;
    if (OB_FAIL(sql.assign_fmt("UPDATE %s SET value = %ld WHERE tenant_id = '%lu' AND name = '%s' "
        "AND value < %ld", OB_ALL_SERVICE_EPOCH_TNAME, epoch_value, tenant_id, name, epoch_value))) {
      LOG_WARN("fail to append sql", KR(ret), K(tenant_id), K(name), K(epoch_value));
    } else if (OB_FAIL(sql_proxy.write(meta_tenant_id, sql.ptr(), affected_rows))) {
      LOG_WARN("fail to execute sql", KR(ret), K(tenant_id), K(meta_tenant_id), K(sql));
    } else {
      FLOG_INFO("succ to update service epoch", K(tenant_id), K(name), K(epoch_value), K(affected_rows));
    }
  }
  return ret;
}

int ObServiceEpochProxy::get_service_epoch(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const char *name,
    int64_t &epoch_value)
{
  return inner_get_service_epoch_(sql_proxy, tenant_id, false, name, epoch_value);
}

int ObServiceEpochProxy::select_service_epoch_for_update(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const char *name,
    int64_t &epoch_value)
{
  return inner_get_service_epoch_(sql_proxy, tenant_id, true, name, epoch_value);
}

int ObServiceEpochProxy::check_service_epoch(
    common::ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const char *name,
    const int64_t expected_epoch,
    bool &is_match)
{
  int ret = OB_SUCCESS;
  is_match = true;
  int64_t persistent_epoch = -1;
  if (OB_UNLIKELY(!is_valid_tenant_id(tenant_id) || (expected_epoch < 0))) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id), K(expected_epoch));
  } else if (OB_FAIL(ObServiceEpochProxy::get_service_epoch(sql_proxy, tenant_id, name, persistent_epoch))) {
    LOG_WARN("fail to get service_epoch", KR(ret), K(tenant_id));
  } else if (persistent_epoch != expected_epoch) {
    is_match = false;
    LOG_WARN("service_epoch mismatch", K(tenant_id), K(expected_epoch), K(persistent_epoch));
  }
  return ret;
}

int ObServiceEpochProxy::inner_get_service_epoch_(
    ObISQLClient &sql_proxy,
    const int64_t tenant_id,
    const bool is_for_update,
    const char *name,
    int64_t &epoch_value)
{
  int ret = OB_SUCCESS;
  if (!is_valid_tenant_id(tenant_id) || OB_ISNULL(name)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id), KP(name));
  } else {
    const uint64_t meta_tenant_id = gen_meta_tenant_id(tenant_id);
    ObSqlString sql;
    SMART_VAR(ObMySQLProxy::MySQLResult, res) {
      ObMySQLResult *result = nullptr;
      if (OB_FAIL(sql.assign_fmt("SELECT * FROM %s WHERE tenant_id = '%lu' AND name = '%s' %s", 
          OB_ALL_SERVICE_EPOCH_TNAME, tenant_id, name, (is_for_update ? "FOR UPDATE" : "")))) {
        LOG_WARN("fail to append sql", KR(ret), K(tenant_id), K(name));
      } else if (OB_FAIL(sql_proxy.read(res, meta_tenant_id, sql.ptr()))) {
        LOG_WARN("fail to execute sql", KR(ret), K(tenant_id), K(meta_tenant_id), K(sql));
      } else if (OB_ISNULL(result = res.get_result())) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("fail to get sql result", KR(ret), K(tenant_id), K(sql));
      } else if (OB_FAIL(result->next())) {
        LOG_WARN("fail to get next", KR(ret), K(tenant_id), K(sql));
      } else {
        EXTRACT_INT_FIELD_MYSQL(*result, "value", epoch_value, int64_t);
      }

      int tmp_ret = OB_SUCCESS;
      if (OB_FAIL(ret)) {
        //nothing todo
      } else if (OB_ITER_END != (tmp_ret = result->next())) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("get more row than one", KR(ret), KR(tmp_ret), K(sql));
      }
    }
  }
  return ret;
}

int ObServiceEpochProxy::check_service_epoch_with_trans(
    ObMySQLTransaction &trans,
    const int64_t tenant_id,
    const char *name,
    const int64_t expected_epoch,
    bool &is_match)
{
  int ret = OB_SUCCESS;
  is_match = true;
  int64_t persistent_epoch = -1;
  if (OB_UNLIKELY(!is_valid_tenant_id(tenant_id) || (expected_epoch < 0))) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", KR(ret), K(tenant_id), K(expected_epoch));
  } else if (OB_FAIL(ObServiceEpochProxy::select_service_epoch_for_update(trans, tenant_id,
                                            name, persistent_epoch))) {
    LOG_WARN("fail to select service_epoch for update", KR(ret), K(tenant_id));
  } else if (persistent_epoch != expected_epoch) {
    is_match = false;
  }
  return ret;
}

} // end namespace share
} // end namespace oceanbase
